package registnet.base;

import grammar.PrerequisiteCruncher;

import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Vector;

import registnet.cpm.Project;
import registnet.cpm.ProjectSource;
import registnet.cpm.CPM;
import registnet.cpm.Node;
import resources.Constants;

/**
 * This class provides default implementations for the <code>IChecklist</code> interface.
 */
public abstract class AbstractChecklist implements IChecklist {

	/**
	 * The student number of the student associated with the checklist.
	 */
	protected String studentNumber;
	
	/**
	 * The plan of coursework of the student associated with the checklist.
	 */
	protected IPlanOfCourseWork planOfCourseWork;
	
	/**
	 * The transcript of the student associated with the checklist.
	 */
	protected ITranscript transcript;
	
	/**
	 * The list of courses associated with the checklist. The course can either be <code>CourseTaken</code> 
	 * or <code>CourseWork</code> or <code>Course</code>.
	 * <p>
	 * <code>CourseTaken</code> are courses that are taken by the student and thus have a grade.<br>
	 * <code>CourseWork</code> are courses that are indicated in the student's plan of coursework.<br>
	 * <code>Course</code> are courses that are indicated in the curriculum of the student. 
	 */
	protected List<ICourse> courses;
	
	/**
	 * The list of recommended courses of the student for the next semester.
	 */
	protected List<ICourse> recommendedCourses;
	
	/**
	 * The list of extra courses taken by the student. Extra courses are courses taken by the 
	 * student which are not specified in the student's curriculum or plan of study.
	 */
	protected List<ICourse> extraCourses;
	
	/**
	 * Update the recommended courses database.
	 */
	private void updateRecommendedCoursesInDatabase(){
		ListIterator<ICourse> li = recommendedCourses.listIterator();
		if(new registnet.dao.StudentRecommendedCoursePeer().getRecommendedCoursesByStudent(new registnet.dao.StudentPeer().getStudentId(studentNumber)).size() != 0)
			new registnet.dao.StudentRecommendedCoursePeer().deleteRecommendedCourses(new registnet.dao.StudentPeer().getStudentId(studentNumber));
		while(li.hasNext()){
			ICourse course = li.next();
			registnet.dao.StudentRecommendedCoursePeer daoStudentRecommendedCoursePeer = new registnet.dao.StudentRecommendedCoursePeer();
			daoStudentRecommendedCoursePeer.createNewStudentRecommendedCourse(new registnet.dao.StudentPeer().getStudentId(studentNumber), new registnet.dao.CoursePeer().getCourseId(course.getCourseCode()));
		}
	}
	
	
	/**
	 * Checks if a course is a dummy course. Dummy courses are courses like GE(AH), PE 2, THESIS etc.
	 * @param course
	 * @return <code>true</code> if the course is a dummy course
	 * and <code>false</code> otherwise
	 */
	private boolean isDummyCourse(ICourse course){
		if(course.getCourseCode().equals("ADDITIONAL"))
			return true;
		if(course.getCourseCode().equals("AND"))
			return true;
		if(course.getCourseCode().equals("CMSCCOURSE"))
			return true;
		if(course.getCourseCode().equals("COGNATE"))
			return true;
		if(course.getCourseCode().equals("COI"))
			return true;
		if(course.getCourseCode().equals("ELECTIVE"))
			return true;
		if(course.getCourseCode().equals("EMPLOYMENT"))
			return true;
		if(course.getCourseCode().equals("FIFTH"))
			return true;
		if(course.getCourseCode().equals("FINAL"))
			return true;
		if(course.getCourseCode().equals("FOURTH"))
			return true;
		if(course.getCourseCode().equals("FRESHMAN"))
			return true;
		if(course.getCourseCode().equals("GE(AH)"))
			return true;
		if(course.getCourseCode().equals("GE(MST)"))
			return true;
		if(course.getCourseCode().equals("GE(SSP)"))
			return true;
		if(course.getCourseCode().equals("GRADUATE"))
			return true;
		if(course.getCourseCode().equals("GRADUATING"))
			return true;
		if(course.getCourseCode().equals("JUNIOR"))
			return true;
		if(course.getCourseCode().equals("LANGUAGE"))
			return true;
		if(course.getCourseCode().equals("MAJOR"))
			return true;
		if(course.getCourseCode().equals("MINOR"))
			return true;
		if(course.getCourseCode().equals("NONE"))
			return true;
		if(course.getCourseCode().equals("NSTP 1"))
			return true;
		if(course.getCourseCode().equals("NSTP 2"))
			return true;
		if(course.getCourseCode().equals("OPTION"))
			return true;
		if(course.getCourseCode().equals("OPTION6"))
			return true;
		if(course.getCourseCode().equals("OR"))
			return true;
		if(course.getCourseCode().equals("PRACTICUM"))
			return true;
		if(course.getCourseCode().equals("SEMINAR"))
			return true;
		if(course.getCourseCode().equals("SENIOR"))
			return true;
		if(course.getCourseCode().equals("SIXTH"))
			return true;
		if(course.getCourseCode().equals("SOCIAL SCIENCE"))
			return true;
		if(course.getCourseCode().equals("SOPHOMORE"))
			return true;
		if(course.getCourseCode().equals("SPECIALIZED"))
			return true;
		if(course.getCourseCode().equals("SUPPORTIVE"))
			return true;
		if(course.getCourseCode().equals("TECHNICAL"))
			return true;
		if(course.getCourseCode().equals("THESIS"))
			return true;
		if(course.getCourseCode().equals("THIRD"))
			return true;
		else return false;
	}
	
	/**
	 * Returns the total number of units of the recommended courses of the student.
	 * @return the total number of units of the recommended courses of the student
	 */
	private int getRecommededCoursesUnits(){
		int retval = 0;
		Iterator<ICourse> i = recommendedCourses.iterator();
		while(i.hasNext()){
			ICourse course = (ICourse)i.next();
			retval += course.getUnit();
		}
		return retval;
	}
	
	/**
	 * Returns a sorted list of courses based on the year and semester in which the courses are supposed 
	 * to be taken by the student.
	 * @param courses - the original list of courses to be sorted
	 * @return a sorted list of courses based on the year and semester in which the courses are supposed 
	 * to be taken by the student
	 */
	private List<ICourse> sortByTimeToTake(List<ICourse> courses){
		List<ICourse> retval = courses;
		int n = retval.size();
		for(int i=0; i<n-1; i++){
			for(int j=0; j<(n-1-i); j++){
				ICourse a = retval.get(j);
				ICourse b = retval.get(j+1);
				if(compareByTimeToTake(a,b)>0){
					retval.set(j,b);
					retval.set(j+1,a);
				}
			}
		}
		return retval;
	}
	
	/**		
	 * Compares two courses based on the year and semester in which the courses are supposed to be taken
	 * by the student.
	 * <p>
	 * Possible return values are <code>-1</code>, <code>0</code> and <code>1</code>.
	 * @param course0
	 * @param course1
	 * @return an <code>int</code> that represents the result of the comparison
	 */
	private int compareByTimeToTake(ICourse course0, ICourse course1){
		if(course0.getYear() > course1.getYear())
			return 1;
		else if(course0.getYear() < course1.getYear())
			return -1;
		else{
			if(course0.getSemester() > course1.getSemester())
				return 1;
			else if(course0.getSemester() < course1.getSemester())
				return -1;
			else{
				return 0;
			}
		}
	}
	
	/**
	 * Checks if a PE 2 course is in the recommended list of the student.
	 * @return <code>true</code> if a PE 2 course is iin the recommended list of the student
	 * and <code>false</code> otherwise
	 */
	private boolean hasRecommendedPE(){
		ListIterator<ICourse> li = recommendedCourses.listIterator();
		while(li.hasNext()){
			ICourse course = li.next();
			if(course.getCourseCode().contains("PE 1") || course.getCourseCode().contains("PE 2") || course.getCourseCode().contains("PE 3")) return true;
		}
		return false;
	}
	
	/**
	 * Returns an <code>int</code> that represents the year level of the student.
	 * @return an <code>int</code> that represents the year level of the student
	 */
	private int getYear(){
		int unitsCredited = transcript.getUnitsCredited();
		int neededUnits = 0;
		try{
			neededUnits = transcript.getCurriculum().getTotalUnits(4) + transcript.getCurriculum().getTotalUnits(3) + transcript.getCurriculum().getTotalUnits(2) + transcript.getCurriculum().getTotalUnits(1); 
			if(unitsCredited > neededUnits){
				return 5;
			}
			neededUnits = transcript.getCurriculum().getTotalUnits(3) + transcript.getCurriculum().getTotalUnits(2) + transcript.getCurriculum().getTotalUnits(1);
			if(unitsCredited > neededUnits){
				return 4;
			}
			neededUnits = transcript.getCurriculum().getTotalUnits(2) + transcript.getCurriculum().getTotalUnits(1);
			if(unitsCredited > neededUnits){
				return 3;
			}
			neededUnits = transcript.getCurriculum().getTotalUnits(1);
			if(unitsCredited > neededUnits){
				return 2;
			}
			return 1;
		}catch(Exception e){
			e.printStackTrace();
		}
		return 0;
	}
	
	@Override
	public Classification getClassification(){
		if ((transcript.getCurriculum().getTotalUnits() - transcript.getUnitsCredited()) < 18)
			return Classification.GRADUATING;
		else if(getYear() == 4 || getYear() == 5)
			return Classification.SENIOR;
		else if(getYear() == 3)
			return Classification.JUNIOR;
		else if(getYear() == 2)
			return Classification.SOPHOMORE;
		else if(getYear() == 1)
			return Classification.FRESHMAN;
		return null;
	}
	
	@Override
	public boolean isRecommended(String courseCode) {
		ListIterator<ICourse> li = recommendedCourses.listIterator();
		while(li.hasNext()){
			ICourse courseTmp = li.next();
			if(courseTmp.getCourseCode().equals(courseCode))
				return true;
		}
		return false;
	}

	@Override
	public boolean isSatisfied(ICourse course) {
		List<Prerequisite> prerequisites = course.getPrerequisites();
		
		//for cases where courseCode has "-" (ex. CMSC 190-1)
		if(course.getCourseCode().contains("-")){
			registnet.dao.CoursePeer daoCoursePeer = new registnet.dao.CoursePeer();
			registnet.dao.Course daoCourse = new registnet.dao.Course();
			String courseCode[] = course.getCourseCode().split("-");
			try{
				daoCourse = daoCoursePeer.getCourse(courseCode[0]);
				List<String> prerequisiteList = PrerequisiteCruncher.getPrerequisites(daoCourse.getPrerequisite());
				ListIterator<String> temp = prerequisiteList.listIterator();
				while(temp.hasNext()){
					Prerequisite prerequisite = new Prerequisite(temp.next());
					prerequisites.add(prerequisite);
				}
				
			}catch(NullPointerException e){ e.printStackTrace(); }
		}
		
		if(prerequisites.size()==1 && prerequisites.get(0).getDescription().equals("COI"))
			return true;
		
		Iterator<Prerequisite> itePrerequisites = prerequisites.iterator();
		while (itePrerequisites.hasNext()){
			Prerequisite prerequisite = itePrerequisites.next();
			String[] tokens = prerequisite.getDescription().split(" AND ");
			
			if(tokens.length== 1){
				//FINAL			TODO improve
				if(tokens[0].equals("FINAL")){ 
					continue;
				}
				//GRADUATE		TODO improve
				else if(tokens[0].equals("GRADUATE")){ 
					continue;
				}
				//COI			TODO improve
				else if(tokens[0].equals("COI")){ 
					continue;
				}
				//NONE
				else if(tokens[0].equals("NONE")){ 
					return true;
				}
				//CLASSIFICATION (ex. SENIOR, JUNIOR)
				else if(Classification.isClassification(tokens[0])){
					if(Classification.isSatisfied(getClassification().getDescription(), tokens[0])) 
						return true;
				}
				//COURSE
				else{
					if(transcript.isCompleted(tokens[0]))
						return true;
				}
			}
			else{
				boolean flag = true;
				for(int ctr=0; ctr<tokens.length; ctr++){
					//FINAL		TODO improve
					if(tokens[ctr].equals("FINAL")){ 
						continue;
					}
					//GRADUATE	TODO improve
					else if(tokens[ctr].equals("GRADUATE")){ 
						continue;
					}
					//COI		TODO improve
					else if(tokens[ctr].equals("COI")){ 
						continue;
					}
					//CLASSIFICATION (ex. SENIOR, JUNIOR)
					else if(Classification.isClassification(tokens[ctr])){
						if(!Classification.isSatisfied(getClassification().getDescription(), tokens[ctr])) 
							flag = false;
					}
					//COURSE
					else{
						if(!transcript.isCompleted(tokens[ctr]))
							flag = false;
					}
				}
				if(flag) return true;
			} 
		}
		return false;
	}

	@Override
	public void addCourse(ICourse course) {
		courses.add(course);
		
	}
	
	@Override
	public boolean contains(ICourse course) {
		ListIterator<ICourse> li = courses.listIterator();
		while(li.hasNext())
			if(course.equals(li.next()))
				return true;
		return false;
	}
	
	@Override
	public boolean contains(String courseCode) {
		ListIterator<ICourse> li = courses.listIterator();
		while(li.hasNext())
			if(courseCode.equals(li.next().getCourseCode()))
				return true;
		return false;
	}
	
	@Override
	public List<ICourse> getAllCourses() {
		return courses;
	}
	
	@Override
	public List<ICourse> getAllCoursesTaken() {
		List<ICourse> coursesTaken = new Vector<ICourse>();
		for(int i=0; i<this.courses.size(); i++){
			ICourse course = this.courses.get(i);
			if(course instanceof CourseTaken)
				coursesTaken.add(course);
		}
		return coursesTaken;
	}
	
	@Override
	public List<ICourse> getAllCoursesToComplete() {
		// TODO Auto-generated method stub
		return null;
	}


	@Override
	public List<ICourse> getAllCoursesToRemove() {
		// TODO TODO Auto-generated method stub
		return null;
	}
	
	@Override
	public List<ICourse> getAllCoursesToTake() {
		List<ICourse> coursesToTake = new Vector<ICourse>();
		for(int i=0; i<this.courses.size(); i++){
			ICourse course = this.courses.get(i);
			//courses that are already taken
			if(course instanceof CourseTaken){
				//add course taken if not yet completed/passed
				if (!transcript.isCompleted(course.getCourseCode()))
					coursesToTake.add(course);
				//ignore if course taken is already completed/passed
				else
					continue;
			//ignore course if it is a dummy course
			}else if(isDummyCourse(course))
				continue;
			//ignore 'PE 2' course (technically a dummy course)
			else if(course.getCourseCode().equals("PE 2"))
				continue;
			//add course which are not yet taken
			else 
				coursesToTake.add(course);
		}
		return coursesToTake;
	}
	
	@Override
	public List<ICourse> getCourses(String courseCode) {
		List<ICourse> retval = new Vector<ICourse>();
		ListIterator<ICourse> li = courses.listIterator();
		while(li.hasNext()){
			ICourse tmpCourse = li.next();
			if(courseCode.equals(tmpCourse.getCourseCode()))
				retval.add(tmpCourse);
		}
		return retval;
	}
	
	@Override
	public List<ICourse> getCourses(int year, int semester) {
		List<ICourse> retval = new Vector<ICourse>();
		ListIterator<ICourse> li = courses.listIterator();
		while(li.hasNext()){
			ICourse tmpCourse = li.next();
			if(year==tmpCourse.getYear() && semester==tmpCourse.getSemester())
				retval.add(tmpCourse);
		}
		return retval;
	}
	
	@Override
	public List<ICourse> getCourses(int year) {
		List<ICourse> retval = new Vector<ICourse>();
		ListIterator<ICourse> li = courses.listIterator();
		while(li.hasNext()){
			ICourse tmpCourse = li.next();
			if(year==tmpCourse.getYear())
				retval.add(tmpCourse);
		}
		return retval;
	}
	
	@Override
	public ICurriculum getCurriculum() {
		return transcript.getCurriculum();
	}
	
	@Override
	public IPlanOfCourseWork getPlanOfCourseWork() {
		return planOfCourseWork;
	}
	
	@Override
	public void determineExtraCourses() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public List<ICourse> getExtraCourses() {
		return extraCourses;
	}

	@Override
	public List<ICourse> getRecommendedCourses() {
		return recommendedCourses;
	}

	@Override
	public void determineRecommendedCourses() {
		
		/*
		 * 	get candidates; courses that have prerequisite courses are completed
		 */
		Project activity = new ProjectSource("RC"+studentNumber, this).getProject();
		CPM cpm = new CPM(activity);
		List<Node> startNodes = cpm.getProject().getStartNodes();
		List<ICourse> startingCourses = new Vector<ICourse>();
		ListIterator<ICourse> li = getAllCoursesToTake().listIterator();
		while(li.hasNext()){
			ICourse course = li.next();
			for(int i=0; i<startNodes.size(); i++)
				if(course.getCourseCode().equals(startNodes.get(i).getName())){
					course.setRank(startNodes.get(i).getSlack());
					startingCourses.add(course);
				}
		}
		
		/*
		 *  case1: follow checklist
		 */
		recommendedCourses = new Vector<ICourse>();
		int nextSemester = (new registnet.dao.SystemPropertiesPeer().getSystemProperties().getSemester() + 1)%3;
		int currentYear = getYear();
		
		boolean flag = true;
		li = getCourses(currentYear, nextSemester).listIterator();
		while(li.hasNext()){
			ICourse checklistCourse = li.next();
			if(isDummyCourse(checklistCourse))
				continue;
			if(coursesListContains(startingCourses, checklistCourse)){
				if(isSatisfied(checklistCourse))
					if(Course.isOffered(checklistCourse.getCourseCode(), nextSemester))
						if((getRecommededCoursesUnits()+checklistCourse.getUnit()) <= getMaximumAllowableLoad())
							recommendedCourses.add(checklistCourse);
			}else{
				flag = false;
				break;
			}
		}if(flag){
			updateRecommendedCoursesInDatabase();
			return;
		}
		
		/*
		 *  case2: checklist is not followed
		 */
		recommendedCourses = new Vector<ICourse>();
		int counter = 0;
		while(true){
			List<ICourse> tmpList = new Vector<ICourse>();
			
			//get list of courses with slack = counter
			li = startingCourses.listIterator();
			while(li.hasNext()){
				ICourse courseToTake = li.next();
				if(courseToTake.getRank() == counter)
					tmpList.add(courseToTake);
			}
			
			//sort by timeToTake(consider semester and year)
			tmpList = sortByTimeToTake(tmpList);
			
			//add
			li = tmpList.listIterator();
			while(li.hasNext() && getRecommededCoursesUnits() < 18){
				ICourse courseToTake = li.next();
				if((courseToTake.getCourseCode().contains("PE 3") || courseToTake.getCourseCode().contains("PE 2") || courseToTake.getCourseCode().contains("PE 1")) && hasRecommendedPE())
					continue;
				if(isSatisfied(courseToTake))
					if(Course.isOffered(courseToTake.getCourseCode(), nextSemester))
						if((getRecommededCoursesUnits()+courseToTake.getUnit()) <= getMaximumAllowableLoad())
							recommendedCourses.add(courseToTake);
			}
			if(startingCourses.size()==recommendedCourses.size() || counter>=startingCourses.size() || getRecommededCoursesUnits()>=18) 
				break;
			counter++;
		}
		updateRecommendedCoursesInDatabase();
	}
	
	/**
	 * Returns the maximum academic load (no. of units) of the student.
	 * @return the maximum academic load (no. of units) of the student
	 */
	private int getMaximumAllowableLoad(){
		Integer studentId = new registnet.dao.StudentPeer().getStudentId(studentNumber);
		String standing = Constants.MAP_STANDING_KEY.get(new registnet.dao.StudentStandingPeer().getLatestStudentStanding(studentId).getStanding());
		
		//student is good standing or warning
		if(standing.equals(Constants.STANDING_GOOD_STANDING) || standing.equals(Constants.STANDING_WARNING)){
			int units = 0;
			int nextSemester = (new registnet.dao.SystemPropertiesPeer().getSystemProperties().getSemester() + 1)%3;
			int currentYear = getYear();
			ListIterator<ICourse> li = getCourses(currentYear, nextSemester).listIterator();
			while(li.hasNext())
				units = units + li.next().getUnit();
			if(units > 18)
				return units;
			else
				return 18;
		}
		//student is under probation
		if(standing.equals(Constants.STANDING_PROBATION))
			return 15;
		//student is dismissed or permanently disqualified
		if(standing.equals(Constants.STANDING_DISMISSED) || standing.equals(Constants.STANDING_PERMANENTLY_DISQUALIFIED))
			return 12;
		return 18;
	}
	
	@Override
	public String getStudentNumber() {
		return studentNumber;
	}
	
	@Override
	public ITranscript getTranscript() {
		return transcript;
	}
	
	@Override
	public void setPlanOfCourseWork(IPlanOfCourseWork planOfCourseWork) {
		this.planOfCourseWork = planOfCourseWork;
	}
	
	@Override
	public void setStudentNumber(String studentNumber) {
		this.studentNumber = studentNumber;
	}
	
	@Override
	public void setTranscript(ITranscript transcript) {
		this.transcript = transcript;
	}
	
	/**
	 * Checks if the given list of course contains the given course.
	 * @param coursesList
	 * @param course
	 * @return true if the given list of course contains the given course, and false otherwise.
	 */
	private boolean coursesListContains(List<ICourse> coursesList, ICourse course){
		ListIterator<ICourse> li = coursesList.listIterator();
		while(li.hasNext())
			if(li.next().getCourseCode().equals(course.getCourseCode()))
				return true;
		return false;
	}

}

